/*
Copyright 2025 PerfXLab (Beijing) Technologies Co., Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include "OpenSpB_Global.h"
#include "OpenSpB_Scalar.h"
#include "OpenSpB_Matrix.h"
#include "OpenSpB_Error.h"
#include "OpenSpB_Type.h"
#include "OpenSpB_Alloc.h"

struct OpenSpB_matrix_opaque OpenSpB_matrix_null_opaque;
OpenSpB_matrix OpenSpB_matrix_null = &OpenSpB_matrix_null_opaque;

OpenSpB_info OpenSpB_matrix_new(
    OpenSpB_matrix *m,
    OpenSpB_type type,
    OpenSpB_index row,
    OpenSpB_index col)
{
    OpenSpB_matrix_format_value_ext format_type;
    if (OpenSpB_Global_is_csc_get())
        format_type = OpenSpB_csc;
    else
        format_type = OpenSpB_csr;
    switch (format_type)
    {
    case (OpenSpB_csr):
        return OpenSpB_matrix_CSC_or_CSR_new(
            m, type, row, col, format_type);
        break;
    case (OpenSpB_csc):
        return OpenSpB_matrix_CSC_or_CSR_new(
            m, type, row, col, format_type);
        break;
    default:
        OpenSpB_Error("Unknown Matrix Format \
in the OpenSpB_matrix_new!\n",
                      __FILE__, __LINE__);
        break;
    }
    return OpenSpB_failure;
}

OpenSpB_info OpenSpB_matrix_new_format_ext(
    OpenSpB_matrix *m, OpenSpB_type type,
    OpenSpB_index row, OpenSpB_index col,
    OpenSpB_matrix_format_value_ext format)
{
    OpenSpB_info info;

    switch (format)
    {
    case (OpenSpB_csr):
        info = OpenSpB_matrix_CSC_or_CSR_new(
            m, type, row, col, format);
        return info;
        break;
    case (OpenSpB_csc):
        info = OpenSpB_matrix_CSC_or_CSR_new(
            m, type, row, col, format);
        return info;
        break;
    default:
        OpenSpB_Error("Unknown Matrix Format \
in the OpenSpB_matrix_new!\n",
                      __FILE__, __LINE__);
        break;
    }
    return OpenSpB_failure;
}

OpenSpB_matrix_format_value_ext OpenSpB_matrix_format_ext(
    const OpenSpB_matrix m)
{
    return m->format_type;
}

OpenSpB_info OpenSpB_matrix_type(
    OpenSpB_type *type, OpenSpB_matrix m)
{
    *type = m->element_type;
    return OpenSpB_success;
}

OpenSpB_info OpenSpB_matrix_nrows(
    OpenSpB_index *n, OpenSpB_matrix m)
{
    *n = m->row;
    return OpenSpB_success;
}
OpenSpB_info OpenSpB_matrix_ncols(
    OpenSpB_index *n, OpenSpB_matrix m)
{
    *n = m->col;
    return OpenSpB_success;
}

#define OpenSpB_MATRIX_FAST_BUILD_EXT(type, T)                     \
    OpenSpB_info OpenSpB_matrix_fast_build_##T##_ext(              \
        OpenSpB_matrix m_in,                                       \
        const OpenSpB_index *row_indices,                          \
        const OpenSpB_index *col_indices,                          \
        const type *values,                                        \
        const OpenSpB_index n)                                     \
    {                                                              \
        OpenSpB_matrix_format_value_ext format =                   \
            m_in->format_type;                                     \
        switch (format)                                            \
        {                                                          \
        case (OpenSpB_csr):                                        \
            return OpenSpB_matrix_CSC_or_CSR_fast_build_##T##_ext( \
                m_in,                                              \
                row_indices,                                       \
                col_indices,                                       \
                values,                                            \
                n);                                                \
            break;                                                 \
        case (OpenSpB_csc):                                        \
            return OpenSpB_matrix_CSC_or_CSR_fast_build_##T##_ext( \
                m_in,                                              \
                row_indices,                                       \
                col_indices,                                       \
                values,                                            \
                n);                                                \
            break;                                                 \
        default:                                                   \
            OpenSpB_Error("Unknown Matrix Format \
in OpenSpB_matrix_build!\n",                                       \
                          __FILE__, __LINE__);                     \
            break;                                                 \
        }                                                          \
        return OpenSpB_failure;                                    \
    }

OpenSpB_MATRIX_FAST_BUILD_EXT(bool, BOOL);
OpenSpB_MATRIX_FAST_BUILD_EXT(int8_t, INT8);
OpenSpB_MATRIX_FAST_BUILD_EXT(int16_t, INT16);
OpenSpB_MATRIX_FAST_BUILD_EXT(int32_t, INT32);
OpenSpB_MATRIX_FAST_BUILD_EXT(int64_t, INT64);
OpenSpB_MATRIX_FAST_BUILD_EXT(uint8_t, UINT8);
OpenSpB_MATRIX_FAST_BUILD_EXT(uint16_t, UINT16);
OpenSpB_MATRIX_FAST_BUILD_EXT(uint32_t, UINT32);
OpenSpB_MATRIX_FAST_BUILD_EXT(uint64_t, UINT64);
OpenSpB_MATRIX_FAST_BUILD_EXT(float, FP32);
OpenSpB_MATRIX_FAST_BUILD_EXT(double, FP64);
#undef OpenSpB_MATRIX_BUILD_EXT

#define OpenSpB_MATRIX_BUILD(type, T)                   \
    OpenSpB_info OpenSpB_matrix_build_##T(              \
        OpenSpB_matrix m_in,                            \
        const OpenSpB_index *row_indices,               \
        const OpenSpB_index *col_indices,               \
        const type *values,                             \
        const OpenSpB_index n)                          \
    {                                                   \
        OpenSpB_matrix_format_value_ext format =        \
            m_in->format_type;                          \
        switch (format)                                 \
        {                                               \
        case (OpenSpB_csr):                             \
            return OpenSpB_matrix_CSC_or_CSR_build_##T( \
                m_in,                                   \
                row_indices,                            \
                col_indices,                            \
                values,                                 \
                n);                                     \
            break;                                      \
        case (OpenSpB_csc):                             \
            return OpenSpB_matrix_CSC_or_CSR_build_##T( \
                m_in,                                   \
                row_indices,                            \
                col_indices,                            \
                values,                                 \
                n);                                     \
            break;                                      \
        default:                                        \
            OpenSpB_Error("Unknown Matrix Format \
in OpenSpB_matrix_build!\n",                            \
                          __FILE__, __LINE__);          \
            break;                                      \
        }                                               \
        return OpenSpB_failure;                         \
    }

OpenSpB_MATRIX_BUILD(bool, BOOL);
OpenSpB_MATRIX_BUILD(int8_t, INT8);
OpenSpB_MATRIX_BUILD(int16_t, INT16);
OpenSpB_MATRIX_BUILD(int32_t, INT32);
OpenSpB_MATRIX_BUILD(int64_t, INT64);
OpenSpB_MATRIX_BUILD(uint8_t, UINT8);
OpenSpB_MATRIX_BUILD(uint16_t, UINT16);
OpenSpB_MATRIX_BUILD(uint32_t, UINT32);
OpenSpB_MATRIX_BUILD(uint64_t, UINT64);
OpenSpB_MATRIX_BUILD(float, FP32);
OpenSpB_MATRIX_BUILD(double, FP64);
#undef OpenSpB_MATRIX_BUILD

#define OpenSpB_MATRIX_SETELEMENT(type, T)                   \
    OpenSpB_info OpenSpB_matrix_set_element_##T(              \
        OpenSpB_matrix m,                                    \
        type value,                                          \
        OpenSpB_index row,                                   \
        OpenSpB_index col)                                   \
    {                                                        \
        OpenSpB_matrix_format_value_ext format =             \
            m->format_type;                                  \
        switch (format)                                      \
        {                                                    \
        case (OpenSpB_csr):                                  \
            return OpenSpB_matrix_CSC_or_CSR_setElement_##T( \
                m, value, row, col);                         \
            break;                                           \
        case (OpenSpB_csc):                                  \
            return OpenSpB_matrix_CSC_or_CSR_setElement_##T( \
                m, value, row, col);                         \
            break;                                           \
        default:                                             \
            OpenSpB_Error("Unknown Matrix Format \
in the OpenSpB_matrix_set_element!\n",                        \
                          __FILE__, __LINE__);               \
            break;                                           \
        }                                                    \
        return OpenSpB_failure;                              \
    }

OpenSpB_MATRIX_SETELEMENT(bool, BOOL);
OpenSpB_MATRIX_SETELEMENT(int8_t, INT8);
OpenSpB_MATRIX_SETELEMENT(int16_t, INT16);
OpenSpB_MATRIX_SETELEMENT(int32_t, INT32);
OpenSpB_MATRIX_SETELEMENT(int64_t, INT64);
OpenSpB_MATRIX_SETELEMENT(uint8_t, UINT8);
OpenSpB_MATRIX_SETELEMENT(uint16_t, UINT16);
OpenSpB_MATRIX_SETELEMENT(uint32_t, UINT32);
OpenSpB_MATRIX_SETELEMENT(uint64_t, UINT64);
OpenSpB_MATRIX_SETELEMENT(float, FP32);
OpenSpB_MATRIX_SETELEMENT(double, FP64);
#undef OpenSpB_MATRIX_SETELEMENT

#define OpenSpB_MATRIX_EXTRACTELEMENT(type, T)                   \
    OpenSpB_info OpenSpB_matrix_extract_element_##T(              \
        type *p,                                                 \
        OpenSpB_matrix m,                                        \
        OpenSpB_index row,                                       \
        OpenSpB_index col)                                       \
    {                                                            \
        OpenSpB_matrix_format_value_ext format =                 \
            m->format_type;                                      \
        switch (format)                                          \
        {                                                        \
        case (OpenSpB_csr):                                      \
            return OpenSpB_matrix_CSC_or_CSR_extractElement_##T( \
                p, m, row, col);                                 \
            break;                                               \
        case (OpenSpB_csc):                                      \
            return OpenSpB_matrix_CSC_or_CSR_extractElement_##T( \
                p, m, row, col);                                 \
            break;                                               \
        default:                                                 \
            OpenSpB_Error("Unknown Matrix Format \
in the OpenSpB_matrix_extract_element!\n",                        \
                          __FILE__, __LINE__);                   \
            break;                                               \
        }                                                        \
        return OpenSpB_failure;                                  \
    }
OpenSpB_MATRIX_EXTRACTELEMENT(bool, BOOL);
OpenSpB_MATRIX_EXTRACTELEMENT(int8_t, INT8);
OpenSpB_MATRIX_EXTRACTELEMENT(int16_t, INT16);
OpenSpB_MATRIX_EXTRACTELEMENT(int32_t, INT32);
OpenSpB_MATRIX_EXTRACTELEMENT(int64_t, INT64);
OpenSpB_MATRIX_EXTRACTELEMENT(uint8_t, UINT8);
OpenSpB_MATRIX_EXTRACTELEMENT(uint16_t, UINT16);
OpenSpB_MATRIX_EXTRACTELEMENT(uint32_t, UINT32);
OpenSpB_MATRIX_EXTRACTELEMENT(uint64_t, UINT64);
OpenSpB_MATRIX_EXTRACTELEMENT(float, FP32);
OpenSpB_MATRIX_EXTRACTELEMENT(double, FP64);
#undef OpenSpB_MATRIX_EXTRACTELEMENT

#define CASE_SWICTH_DATA_TYPE_A_TO_B(   \
    type_A, T_A, type_B, T_B)           \
    case OpenSpB_##T_B:                 \
        s->value.T_B##_value =          \
            (type_B)(temp.T_A##_value); \
        break;

#define CASES_SWICTH_DATA_TYPE_A_TO_B(type_A, T_A) \
    switch (s->element_type)                       \
    {                                              \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, bool, BOOL);              \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, int8_t, INT8);            \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, int16_t, INT16);          \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, int32_t, INT32);          \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, int64_t, INT64);          \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, uint8_t, UINT8);          \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, uint16_t, UINT16);        \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, uint32_t, UINT32);        \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, uint64_t, UINT64);        \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, float, FP32);             \
        CASE_SWICTH_DATA_TYPE_A_TO_B(              \
            type_A, T_A, double, FP64);            \
    default:                                       \
        OpenSpB_Error("unknown element type \
of the scalar !\n",                                \
                      __FILE__, __LINE__);         \
        break;                                     \
    }

#define CASE_EXTRACTELEMENT_SCALAR(type_A, T_A)         \
    case OpenSpB_##T_A:                                 \
        info = OpenSpB_matrix_extract_element_##T_A(     \
            &(temp.T_A##_value), m, row, col);          \
        if (info == OpenSpB_success)                    \
            CASES_SWICTH_DATA_TYPE_A_TO_B(type_A, T_A); \
        break;

#define CASES_EXTRACTELEMENT_SCALAR               \
    CASE_EXTRACTELEMENT_SCALAR(bool, BOOL);       \
    CASE_EXTRACTELEMENT_SCALAR(int8_t, INT8);     \
    CASE_EXTRACTELEMENT_SCALAR(int16_t, INT16);   \
    CASE_EXTRACTELEMENT_SCALAR(int32_t, INT32);   \
    CASE_EXTRACTELEMENT_SCALAR(int64_t, INT64);   \
    CASE_EXTRACTELEMENT_SCALAR(uint8_t, UINT8);   \
    CASE_EXTRACTELEMENT_SCALAR(uint16_t, UINT16); \
    CASE_EXTRACTELEMENT_SCALAR(uint32_t, UINT32); \
    CASE_EXTRACTELEMENT_SCALAR(uint64_t, UINT64); \
    CASE_EXTRACTELEMENT_SCALAR(float, FP32);      \
    CASE_EXTRACTELEMENT_SCALAR(double, FP64);

OpenSpB_info OpenSpB_matrix_extract_element_Scalar(
    OpenSpB_scalar s,
    const OpenSpB_matrix m,
    const OpenSpB_index row,
    const OpenSpB_index col)
{
    OpenSpB_info info;
    OpenSpB_type m_tpye = m->element_type;
    Scalar_Value temp;
    switch (m_tpye)
    {
        CASES_EXTRACTELEMENT_SCALAR;
    default:
        OpenSpB_Error("Unknown the scalar element type\
of the input Matrix \
in the OpenSpB_matrix_extract_element_Scalar\n",
                      __FILE__, __LINE__);
        info = OpenSpB_failure;
        break;
    }
    return info;
}
#undef CASES_EXTRACTELEMENT_SCALAR
#undef CASE_EXTRACTELEMENT_SCALAR
#undef CASES_SWICTH_DATA_TYPE_A_TO_B
#undef CASE_SWICTH_DATA_TYPE_A_TO_B

OpenSpB_info OpenSpB_matrix_free(OpenSpB_matrix *m)
{
    OpenSpB_matrix_format_value_ext format =
        (*m)->format_type;
    switch (format)
    {
    case (OpenSpB_csr):
        return OpenSpB_matrix_CSC_or_CSR_free(m);
        break;
    case (OpenSpB_csc):
        return OpenSpB_matrix_CSC_or_CSR_free(m);
        break;
    default:
        OpenSpB_Error("Unknown Matrix Format in \
OpenSpB_matrix_free!\n",
                      __FILE__, __LINE__);
        break;
    }
    return OpenSpB_failure;
}

///////////////////////////////////Build CSC or CSR////////////////////////////////

#define Matrix_compute_next_grow(size) \
    ((size) ? ((size) << 1) : 1)

static OpenSpB_info OpenSpB_matrix_CSC_or_CSR_grow(
    OpenSpB_matrix_CSC_or_CSR *m,
    OpenSpB_index capacity)
{
    size_t size = m->matrix_base.element_size;
    if (m->val)
        m->val = OpenSpB_aligned_realloc(
            m->val, size * capacity);
    else
        m->val = OpenSpB_aligned_malloc(
            size * capacity);
    assert(m->val);
    if (m->indices)
        m->indices =
            (OpenSpB_index *)OpenSpB_aligned_realloc(
                m->indices, sizeof(OpenSpB_index) * capacity);
    else
        m->indices = (OpenSpB_index *)OpenSpB_aligned_malloc(
            sizeof(OpenSpB_index) * capacity);
    assert(m->indices);
    m->capacity = capacity;
    return OpenSpB_success;
}

/**
 * @brief Create a matrix of CSC/CSR format.
 * @param[out] m_in the pointer to a matrix
 */
OpenSpB_info OpenSpB_matrix_CSC_or_CSR_new(
    OpenSpB_matrix *m_in,
    OpenSpB_type type,
    OpenSpB_index row,
    OpenSpB_index col,
    OpenSpB_matrix_format_value_ext format_type)
{
    assert(format_type == OpenSpB_csc ||
           format_type == OpenSpB_csr);
    OpenSpB_matrix_CSC_or_CSR **m =
        (OpenSpB_matrix_CSC_or_CSR **)m_in;
    OK_ALLOC(*m = (OpenSpB_matrix_CSC_or_CSR *)OpenSpB_aligned_malloc(
                 sizeof(OpenSpB_matrix_CSC_or_CSR)));
    (*m_in)->element_type = type;
    (*m_in)->format_type = format_type;
    (*m)->matrix_base.row = row;
    (*m)->matrix_base.col = col;
    (*m)->ptr_len =
        format_type == OpenSpB_csc
            ? (col + 1)
            : (row + 1);
    (*m)->nnz = 0;
    switch (type)
    {
        OpenSpB_TYPE_CASES(m_in);
    }
    (*m)->capacity = 0;
    (*m)->val = NULL;
    (*m)->indices = NULL;
    OpenSpB_index p_len =
        (*m)->matrix_base.format_type == OpenSpB_csc
            ? (row + 1)
            : (col + 1);
    OK_ALLOC((*m)->ptr = (OpenSpB_index *)OpenSpB_aligned_calloc(
                 p_len * sizeof(OpenSpB_index)));

    return OpenSpB_success;
}

#define GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(type, T)            \
    OpenSpB_info OpenSpB_matrix_CSC_or_CSR_fast_build_##T##_ext( \
        OpenSpB_matrix m_in,                                     \
        const OpenSpB_index *row_indices,                        \
        const OpenSpB_index *col_indices,                        \
        const type *values,                                      \
        const OpenSpB_index n)                                   \
    {                                                            \
        OpenSpB_matrix_CSC_or_CSR *m =                           \
            (OpenSpB_matrix_CSC_or_CSR *)m_in;                   \
        OpenSpB_matrix_CSC_or_CSR_grow(m, n);                    \
                                                                 \
        m->nnz = n;                                              \
                                                                 \
        m->ptr[0] = 0;                                           \
        OpenSpB_index ptr_index = 0;                             \
                                                                 \
        const OpenSpB_index *p1_indices =                        \
            m_in->format_type == OpenSpB_csc                     \
                ? row_indices                                    \
                : col_indices;                                   \
        const OpenSpB_index *p2_indices =                        \
            m_in->format_type == OpenSpB_csc                     \
                ? col_indices                                    \
                : row_indices;                                   \
        for (OpenSpB_index i = 0; i < n; ++i)                    \
        {                                                        \
            m->indices[i] = p1_indices[i];                       \
            ((type *)(m->val))[i] = values[i];                   \
            while (ptr_index != p2_indices[i])                   \
            {                                                    \
                m->ptr[++ptr_index] = i;                         \
            }                                                    \
        }                                                        \
        m->ptr[m->ptr_len - 1] = n;                              \
        return OpenSpB_success;                                  \
    }
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(bool, BOOL);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(int8_t, INT8);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(int16_t, INT16);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(int32_t, INT32);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(int64_t, INT64);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(uint8_t, UINT8);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(uint16_t, UINT16);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(uint32_t, UINT32);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(uint64_t, UINT64);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(float, FP32);
GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT(double, FP64);
#undef GRB_MATRIX_CSC_OR_CSR_FAST_BUILD_EXT

typedef union
{
    bool VAL_BOOL;
    int8_t VAL_INT8;
    int16_t VAL_INT16;
    int32_t VAL_INT32;
    int64_t VAL_INT64;
    uint8_t VAL_UINT8;
    uint16_t VAL_UINT16;
    uint32_t VAL_UINT32;
    uint64_t VAL_UINT64;
    float VAL_FP32;
    double VAL_FP64;
} OpenSpB_Build_Object_Format_ext;

typedef struct triple
{
    OpenSpB_index row;
    OpenSpB_index col;
    OpenSpB_Build_Object_Format_ext val;
} triple;

int tri_cmp_csr(const void *a, const void *b)
{
    triple *a1 = (triple *)a;
    triple *a2 = (triple *)b;
    if ((*a1).row > (*a2).row)
        return 1;
    else if ((*a1).row < (*a2).row)
        return -1;
    else if ((*a1).row == (*a2).row && (*a1).col > (*a2).col)
        return 1;
    else if ((*a1).row == (*a2).row && (*a1).col < (*a2).col)
        return -1;
    return 0;
}
int tri_cmp_csc(const void *a, const void *b)
{
    triple *a1 = (triple *)a;
    triple *a2 = (triple *)b;
    if ((*a1).col > (*a2).col)
        return 1;
    else if ((*a1).col < (*a2).col)
        return -1;
    else if ((*a1).col == (*a2).col && (*a1).row > (*a2).row)
        return 1;
    else if ((*a1).col == (*a2).col && (*a1).row < (*a2).row)
        return -1;
    return 0;
}

#define OpenSpB_MATRIX_CSC_OR_CSR_BUILD(type, T)                          \
    OpenSpB_info OpenSpB_matrix_CSC_or_CSR_build_##T(                     \
        OpenSpB_matrix m_in,                                              \
        const OpenSpB_index *row_indices,                                 \
        const OpenSpB_index *col_indices,                                 \
        const type *values,                                               \
        const OpenSpB_index n)                                            \
    {                                                                     \
        OpenSpB_matrix_CSC_or_CSR *m = (OpenSpB_matrix_CSC_or_CSR *)m_in; \
        OpenSpB_matrix_CSC_or_CSR_grow(m, n);                             \
                                                                          \
        m->nnz = n;                                                       \
        triple *NNZ_TRIPLE = (triple *)malloc(n * sizeof(triple));        \
        memset(NNZ_TRIPLE, 0, sizeof(triple) * n);                        \
        for (OpenSpB_index i = 0; i < n; i++)                             \
        {                                                                 \
            NNZ_TRIPLE[i].row = row_indices[i];                           \
            NNZ_TRIPLE[i].col = col_indices[i];                           \
            NNZ_TRIPLE[i].val.VAL_##T = values[i];                        \
        }                                                                 \
        if (m_in->format_type == OpenSpB_csc)                             \
        {                                                                 \
            qsort(NNZ_TRIPLE, n, sizeof(NNZ_TRIPLE[0]), tri_cmp_csc);     \
        }                                                                 \
        else                                                              \
        {                                                                 \
            qsort(NNZ_TRIPLE, n, sizeof(NNZ_TRIPLE[0]), tri_cmp_csr);     \
        }                                                                 \
                                                                          \
        m->ptr[0] = 0;                                                    \
        OpenSpB_index ptr_index = 0;                                      \
                                                                          \
        if (m_in->format_type == OpenSpB_csc)                             \
        {                                                                 \
            for (OpenSpB_index i = 0; i < n; ++i)                         \
            {                                                             \
                m->indices[i] = NNZ_TRIPLE[i].row;                        \
                ((type *)(m->val))[i] = NNZ_TRIPLE[i].val.VAL_##T;        \
                while (ptr_index != NNZ_TRIPLE[i].col)                    \
                {                                                         \
                    m->ptr[++ptr_index] = i;                              \
                }                                                         \
            }                                                             \
            m->ptr[m->ptr_len - 1] = n;                                   \
            return OpenSpB_success;                                       \
        }                                                                 \
        else                                                              \
        {                                                                 \
            for (OpenSpB_index i = 0; i < n; ++i)                         \
            {                                                             \
                m->indices[i] = NNZ_TRIPLE[i].col;                        \
                ((type *)(m->val))[i] = NNZ_TRIPLE[i].val.VAL_##T;        \
                while (ptr_index != NNZ_TRIPLE[i].row)                    \
                {                                                         \
                    m->ptr[++ptr_index] = i;                              \
                }                                                         \
            }                                                             \
            m->ptr[m->ptr_len - 1] = n;                                   \
            return OpenSpB_success;                                       \
        }                                                                 \
    }
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(bool, BOOL);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(int8_t, INT8);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(int16_t, INT16);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(int32_t, INT32);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(int64_t, INT64);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(uint8_t, UINT8);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(uint16_t, UINT16);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(uint32_t, UINT32);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(uint64_t, UINT64);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(float, FP32);
OpenSpB_MATRIX_CSC_OR_CSR_BUILD(double, FP64);
#undef OpenSpB_MATRIX_CSC_OR_CSR_BUILD

OpenSpB_index OpenSpB_matrix_CSC_or_CSR_capacity(OpenSpB_matrix_CSC_or_CSR *m)
{
    return m->capacity;
}

// This function is created by xulei!
// It is designed for the OpenSpB_matrix_set_element.
// It includes many meanings and needs to be decomposed.
// It will check if the element(row, col) is stored in the CSC matrix(m).
// If true, it provides the address of the element through the pointer p.
// If false, it provides the index of the element to be added to the CSC matrix soon, and returns the OpenSpB_no_value.
static OpenSpB_info OpenSpB_matrix_CSC_or_CSR_findElement(
    void **p,
    OpenSpB_matrix_CSC_or_CSR *m,
    OpenSpB_index row,
    OpenSpB_index col,
    OpenSpB_index *index)
{
    OpenSpB_index index1 =
        m->matrix_base.format_type == OpenSpB_csc ? col : row;
    OpenSpB_index index2 =
        m->matrix_base.format_type == OpenSpB_csc ? row : col;

    OpenSpB_index ptr_start = m->ptr[index1];
    *index = ptr_start;
    OpenSpB_index n_one_line = m->ptr[index1 + 1] - ptr_start;
    size_t size = m->matrix_base.element_size;
    for (OpenSpB_index i = 0; i < n_one_line; ++i)
    {
        OpenSpB_index iter = i + ptr_start;
        OpenSpB_index index2_temp = m->indices[iter];
        if (index2 == index2_temp)
        {
            *p = m->val + size * iter;
            return OpenSpB_success;
        }
        else if (index2 < index2_temp)
        {
            *index = iter;
            break;
        }
        else
        {
            *index = iter + 1;
        }
    }
    return OpenSpB_no_value;
}

// This function is a binarySearch version of a OpenSpB_matrix_findElement.
// TODO The binarySearch for the Index of val/row array in a OpenSpB_matrix_Contents with a column number(target), where the start and end locate a specific line in a CSC sparse matrix and the number of elements in this row is greater than 1.
// OpenSpB_index binarySearch(OpenSpB_index *list, OpenSpB_index start, OpenSpB_index end, OpenSpB_index target)
// {
//     end = end - 1;
//     assert(list[start] <= list[end]);
//     while (start <= end)
//     {
//         OpenSpB_index middle = (start + end) / 2;
//     }
//     return OpenSpB_success;
// }

#define OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(type, T)                  \
    OpenSpB_info OpenSpB_matrix_CSC_or_CSR_setElement_##T(             \
        OpenSpB_matrix m_in,                                           \
        type value,                                                    \
        OpenSpB_index row,                                             \
        OpenSpB_index col)                                             \
    {                                                                  \
        OpenSpB_matrix_CSC_or_CSR *m =                                 \
            (OpenSpB_matrix_CSC_or_CSR *)m_in;                         \
        size_t size = m_in->element_size;                              \
        OpenSpB_index index1 =                                         \
            m->matrix_base.format_type == OpenSpB_csc                  \
                ? col                                                  \
                : row;                                                 \
        OpenSpB_index index2 =                                         \
            m->matrix_base.format_type == OpenSpB_csc                  \
                ? row                                                  \
                : col;                                                 \
        if (value)                                                     \
        {                                                              \
            void *p = 0;                                               \
            assert(row <= m->matrix_base.row ||                        \
                   col <= m->matrix_base.col);                         \
                                                                       \
            OpenSpB_index index;                                       \
                                                                       \
            if (OpenSpB_matrix_CSC_or_CSR_findElement(                 \
                    &p, m, row, col, &index))                          \
            {                                                          \
                OpenSpB_index m_capacity =                             \
                    OpenSpB_matrix_CSC_or_CSR_capacity(m);             \
                OpenSpB_index m_size =                                 \
                    OpenSpB_matrix_CSC_or_CSR_nnz(m);                  \
                if (m_capacity <= m_size)                              \
                {                                                      \
                    OpenSpB_matrix_CSC_or_CSR_grow(                    \
                        m,                                             \
                        Matrix_compute_next_grow((m_capacity)));       \
                }                                                      \
                if (index < m_size)                                    \
                {                                                      \
                    memmove(m->val + (index + 1) * size,               \
                            m->val + index * size,                     \
                            size * (m_size - index));                  \
                                                                       \
                    memmove(m->indices + index + 1,                    \
                            m->indices + index,                        \
                            sizeof(OpenSpB_index) * (m_size - index)); \
                }                                                      \
                ((type *)m->val)[index] = value;                       \
                m->indices[index] = index2;                            \
                for (                                                  \
                    OpenSpB_index i = index1 + 1;                      \
                    i < m->ptr_len;                                    \
                    i++)                                               \
                {                                                      \
                    m->ptr[i]++;                                       \
                }                                                      \
                m->nnz++;                                              \
            }                                                          \
            else                                                       \
            {                                                          \
                *((type *)p) = value;                                  \
            }                                                          \
            return OpenSpB_success;                                    \
        }                                                              \
        else                                                           \
        {                                                              \
            return OpenSpB_failure;                                    \
        }                                                              \
    }

OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(bool, BOOL);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(int8_t, INT8);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(int16_t, INT16);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(int32_t, INT32);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(int64_t, INT64);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(uint8_t, UINT8);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(uint16_t, UINT16);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(uint32_t, UINT32);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(uint64_t, UINT64);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(float, FP32);
OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT(double, FP64);
#undef OpenSpB_MATRIX_CSC_OR_CSR_SETELEMENT

#define OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(type, T)          \
    OpenSpB_info OpenSpB_matrix_CSC_or_CSR_extractElement_##T(     \
        type *p,                                                   \
        OpenSpB_matrix m_in,                                       \
        OpenSpB_index row,                                         \
        OpenSpB_index col)                                         \
    {                                                              \
        if (m_in->element_type != OpenSpB_##T)                     \
            OpenSpB_Error("The data types of the matrix elements and the target do not match \
in OpenSpB_matrix_CSC_or_CSR_extractElement!\n",                   \
                          __FILE__, __LINE__);                     \
        OpenSpB_matrix_CSC_or_CSR *m =                             \
            (OpenSpB_matrix_CSC_or_CSR *)(m_in);                   \
                                                                   \
        OpenSpB_index index1 =                                     \
            m_in->format_type == OpenSpB_csc ? col : row;          \
        OpenSpB_index index2 =                                     \
            m_in->format_type == OpenSpB_csc ? row : col;          \
                                                                   \
        OpenSpB_index ptr_start = m->ptr[index1];                  \
        OpenSpB_index n_one_line =                                 \
            m->ptr[index1 + 1] - ptr_start;                        \
                                                                   \
        for (OpenSpB_index i = 0; i < n_one_line; ++i)             \
        {                                                          \
            OpenSpB_index index2_temp = m->indices[i + ptr_start]; \
            if (index2 == index2_temp)                             \
            {                                                      \
                *p = ((type *)m->val)[i + ptr_start];              \
                return OpenSpB_success;                            \
            }                                                      \
            else if (index2 < index2_temp)                         \
                break;                                             \
        }                                                          \
        return OpenSpB_no_value;                                   \
    }

OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(bool, BOOL);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(int8_t, INT8);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(int16_t, INT16);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(int32_t, INT32);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(int64_t, INT64);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(uint8_t, UINT8);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(uint16_t, UINT16);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(uint32_t, UINT32);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(uint64_t, UINT64);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(float, FP32);
OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT(double, FP64);
#undef OpenSpB_MATRIX_CSC_OR_CSR_EXTRACTELEMENT

OpenSpB_index OpenSpB_matrix_CSC_or_CSR_nnz(OpenSpB_matrix_CSC_or_CSR *m)
{
    return m->nnz;
}

OpenSpB_info OpenSpB_matrix_CSC_or_CSR_free(OpenSpB_matrix *m_in)
{
    OpenSpB_matrix_CSC_or_CSR **m = (OpenSpB_matrix_CSC_or_CSR **)m_in;
    if ((*m)->val)
    {
        free((*m)->val);
        free((*m)->indices);
        free((*m)->ptr);

        free(*m);
        return OpenSpB_success;
    }
    else
    {
        return OpenSpB_failure;
    }
}